/**
 * 
 */
package me.twoday.library.mine.common.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import me.twoday.library.mine.common.LoggerUtil;
import me.twoday.library.mine.common.base.BaseApplication;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.json.JSONObject;

import android.content.Context;
import android.os.Message;

/**
 * HTTP 요청을 처리하기 위한 클래스
 * 
 * HTTP_CLIENT_TIMEOUT: 서버로 요청의 응답을 기다릴 타임아웃 시간 (millisecond)
 * HTTP_CLIENT_MAX_RETRY_COUNT: 요청 실패시 재시도할 횟수
 * DEFAULT_CHARACTER_SET: 통신에 사용되는 기본 문자열 캐릭터셋
 * 
 * @author 박우람
 * @since 2011.02.13
 */
public abstract class HttpConnection implements Runnable
{
	private static final String TAG = "HttpConnection";
	private static final int HTTP_CLIENT_TIMEOUT = 10000;
	protected static final String DEFAULT_CHARACTER_SET = "UTF-8";
	
	public static final int HTTP_DID_START = 0;
	public static final int HTTP_DID_ERROR = 1;
	public static final int HTTP_DID_SUCCEED = 2;
	
	public static final int HTTP_RESPONSE_TEXT = 0;
	public static final int HTTP_RESPONSE_JSON = 1;
	
	private final LoggerUtil mLog;
	
	protected DefaultHttpClient mHttpClient;
	protected HttpHandler mHttpHandler;
	protected Context mContext;
	protected String mGateway;
	protected String mUrl;
	protected int mResponseType;
	protected int mMaxRetryCount;
	protected int mCurrentRetryCount;
	
	/**
	 * 기본 생성자
	 */
	public HttpConnection(HttpHandler httpHandler)
	{
		this.mHttpHandler = httpHandler;
		this.mContext = BaseApplication.getContext();
		this.mMaxRetryCount = 0;
		this.mCurrentRetryCount = 0;
		this.mLog = LoggerUtil.getInstance(TAG);
	}
	
	/**
	 * 기본적으로 통신할 게이트웨이 서버를 설정한다.
	 * 
	 * @param gateway 게이트웨이 주소
	 */
	public void setGateway(String gateway)
	{
		this.mGateway = gateway;
	}
	
	/**
	 * 통신 실패시 자동으로 재시도를 할 횟수를 설정한다.
	 * 
	 * @param retryCount 재시도 횟수
	 */
	public void setMaxRetryCount(int retryCount)
	{
		this.mMaxRetryCount = retryCount;
	}
	
	/**
	 * JSON 요청을 실행하는 메서드
	 * 
	 * @param url 접속할 URL
	 */
	public void executeJson(String url)
	{
		execute(url, HTTP_RESPONSE_JSON);
	}
	
	/**
	 * TEXT 요청을 실행하는 메서드
	 * 
	 * @param url 접속할 URL
	 */
	public void executeText(String url)
	{
		execute(url, HTTP_RESPONSE_TEXT);
	}
	
	/**
	 * 실제로 HTTP 요청을 실행하는 메서드
	 * 
	 * @param url 접속할 URL
	 * @param method 응답 방식
	 */
	private void execute(String url, int responseType)
	{
		if(mGateway != null)
		{
			this.mUrl = mGateway + url;
			this.mResponseType = responseType;
			
			// 네트워크 사용이 가능한지 여부를 확인한다.
			ConnectionManager.getInstance().push(this);
		}
		else
		{
			mLog.e("접속할 게이트웨이 서버 주소가 정의되지 않았습니다");
		}
	}
	
	/**
	 * 전처리 메서드
	 */
	protected void executePreProcess()
	{
		mHttpHandler.sendMessage(Message.obtain(mHttpHandler, HttpConnection.HTTP_DID_START));
		this.mHttpClient = new DefaultHttpClient();
		HttpConnectionParams.setConnectionTimeout(mHttpClient.getParams(), HTTP_CLIENT_TIMEOUT);
		HttpConnectionParams.setSoTimeout(mHttpClient.getParams(), HTTP_CLIENT_TIMEOUT * 2);
	}
	
	/**
	 * 후처리 메서드
	 * 
	 * @param response 요청 응답
	 * @throws IllegalStateException 상태 예외
	 * @throws IOException IO 예외
	 */
	protected boolean executePostProcess(HttpResponse response) throws IllegalStateException, IOException
	{
		boolean result = response.getEntity() != null;
		
		processHeader(response.getAllHeaders()); 
		processEntity(response.getEntity());
		
		ConnectionManager.getInstance().didComplete(this);
		
		return result;
	}
	
	/**
	 * 전역변수로 저장된 현재의 쿠키정보를 반환한다
	 * 
	 * @return 쿠키값
	 */
	protected String getCookieSessionKey()
	{
		String sessions = BaseApplication.getSessionKey();
		
		if(sessions == null)
		{
			return "";
		}
		
		return sessions; 
	}
	
	/**
	 * 헤더 목록중에 쿠키 정보를 전역변수로 저장한다.
	 * 
	 * @param headers 헤더목록
	 */
	private void processHeader(Header[] headers)
	{
		String sessionKeys = "";
		for(Header header : headers)
		{
			if(header.getName().equals("Set-Cookie"))
			{
				sessionKeys += header.getValue().trim() + ";";
			}
		}
		
		if(sessionKeys.length() > 0)
		{
			BaseApplication.setSessionKey(sessionKeys);
		}
		
		
	}

	/**
	 * 결과값을 문자열로 변환하기 위해 사용하는 메서드
	 * 
	 * @param entity HTTP 결과 객체
	 * @throws Exception
	 */
	private void processEntity(HttpEntity entity) throws IllegalStateException, IOException
	{
		InputStream inputStreram = entity.getContent();
		InputStreamReader inputStreamReader = new InputStreamReader(inputStreram, "UTF-8");
		
		StringBuffer result = new StringBuffer();
		
		try
		{
			BufferedReader br = new BufferedReader(inputStreamReader);
			
			String line;

			while((line = br.readLine()) != null)
			{
				result.append(line);
			}
		}
		finally
		{
			inputStreram.close();
			inputStreamReader.close();
		}
		
		mLog.d("Result: " + result.toString());
		
		try
		{
			switch(mResponseType)
			{
			// 일반적인 응답을 단순 텍스트로 처리한다.
			case HTTP_RESPONSE_TEXT:
				mHttpHandler.sendMessage(Message.obtain(mHttpHandler, HttpConnection.HTTP_DID_SUCCEED, result));
				break;

			// 응답을 JSON으로 처리한다.
			case HTTP_RESPONSE_JSON:
				// 결과값이 JSON으로 정상 변환되는지 확인한다.
				JSONObject resultObject = new JSONObject(result.toString());
				boolean success = resultObject.getBoolean("success");
				
				// 결과 값이 정상이라면
				if(success)
				{
					mHttpHandler.sendMessage(Message.obtain(mHttpHandler, HttpConnection.HTTP_DID_SUCCEED, resultObject));
					return;
				}
				// 결과값에 문제가 있을 경우
				else
				{
					String errMessage = resultObject.getString("message");
					mHttpHandler.sendMessage(Message.obtain(mHttpHandler, HttpConnection.HTTP_DID_ERROR, new MsgException(errMessage)));
				}
				break;
			}
		}
		catch(Exception e)
		{
			processNetworkError(e);
		}
	}
	
	/**
	 * 네트워크 상태 문제로 인한 오류 처리는 이곳에서 한다.
	 * 
	 * @param e Exception 객체
	 */
	protected void processNetworkError(Exception e)
	{
		// 재시도가 가능할 경우 재시도를 한다.
		if(mCurrentRetryCount < mMaxRetryCount)
		{
			try
			{
				this.mCurrentRetryCount++;
				mLog.e("네트워크 통신 오류 - " + mCurrentRetryCount + "차 재시도");
				
				Thread.sleep(500);
				ConnectionManager.getInstance().didComplete(this);
				ConnectionManager.getInstance().push(this);
			}
			catch(InterruptedException e1) {}
		}
		// 에러처리 루틴으로 넘김
		else
		{
			mHttpHandler.sendMessage(Message.obtain(mHttpHandler, HttpConnection.HTTP_DID_ERROR, e));
		}
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public abstract void run();
}
